package com.fansl.allround.auth.provider.sys.openid;

import cn.hutool.core.util.StrUtil;
import com.fansl.allround.auth.provider.social.SocialLoginInfo;
import com.fansl.allround.auth.provider.social.qq.api.QQApi;
import com.fansl.allround.auth.provider.social.qq.bean.QQOpenId;
import com.fansl.allround.auth.provider.social.qq.bean.QQToken;
import com.fansl.allround.auth.provider.social.weibo.api.WeiboApi;
import com.fansl.allround.auth.provider.social.weibo.bean.WeiboToken;
import com.fansl.allround.common.core.constant.SecurityConstants;
import com.fansl.allround.common.core.constant.enums.OauthTypeEnum;
import com.fansl.allround.common.core.constant.enums.ServiceTypeEnum;
import lombok.Setter;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

/**
 * @author fansl
 * @Description: ucenter用户登录验证filter
 * @date 2019/9/26 14:22
 */
public class SysUserOpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    @Setter
    private QQApi qqApi;
    @Setter
    private WeiboApi weiboApi;
    @Setter
    private RedisTemplate redisTemplate;

    public static final String SPRING_SECURITY_FORM_PROVIDER_ID_KEY = "providerId";
    public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private String providerIdParameter = SPRING_SECURITY_FORM_PROVIDER_ID_KEY;
    private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = false;

    public SysUserOpenIdAuthenticationFilter() {
        super(new AntPathRequestMatcher(SecurityConstants.SOCIAL_TOKEN_URL, HttpMethod.POST.name()));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {

            String providerId = this.obtainProviderId(request);
            String code = this.obtainCode(request);
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (StrUtil.isBlank(providerId)) {
                throw new AuthenticationServiceException("providerId parameter must not be empty or null");
            }
            if (StrUtil.isBlank(code)) {
                throw new AuthenticationServiceException("code parameter must not be empty or null");
            }
            if (username == null) {
                username = "";
            }
            if (password == null) {
                password = "";
            }

            providerId = providerId.trim();
            code = code.trim();
            username = username.trim();
            password = password.trim();
            String openId = null;
            String accessToken = null;
            OauthTypeEnum oauthType = OauthTypeEnum.getByCode(Integer.parseInt(providerId));
            //获取redis中的SocialLoginInfo
            String socialLoginOpenIdKey =
                    String.format(SecurityConstants.SOCIAL_LOGIN_OPENID_KEY, ServiceTypeEnum.BACKEND.name(), oauthType.name(), code);
            Object socialLoginInfoObj = redisTemplate.opsForValue().get(socialLoginOpenIdKey);
            if (Objects.nonNull(socialLoginInfoObj)) {
                SocialLoginInfo socialLoginInfo = (SocialLoginInfo) socialLoginInfoObj;
                openId = socialLoginInfo.getOpenId();
                accessToken = socialLoginInfo.getAccessToken();
            } else {
                switch (oauthType) {
                    case QQ: {
                        QQToken qqToken = qqApi.getAccessToken(code);
                        if (qqToken != null) {
                            accessToken = qqToken.getAccessToken();
                            QQOpenId qqOpenId = qqApi.getOpenId(accessToken);
                            openId = qqOpenId.getOpenId();
                        }
                        break;
                    }
                    case WEIBO:
                        WeiboToken weiboToken = weiboApi.getAccessToken(code);
                        if (weiboToken != null) {
                            accessToken = weiboToken.getAccessToken();
                            openId = weiboToken.getUid();
                        }
                        break;
                    default:
                        throw new AuthenticationServiceException("oauth type not support");
                }
            }
            if (StrUtil.isBlank(openId)) {
                throw new AuthenticationServiceException("openid get error");
            }
            SysUserOpenIdAuthenticationToken authRequest =
                    new SysUserOpenIdAuthenticationToken(openId, providerId, accessToken, username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected void setDetails(HttpServletRequest request, SysUserOpenIdAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    protected String obtainProviderId(HttpServletRequest request) {
        return request.getParameter(providerIdParameter);
    }

    public void setProviderIdParameter(String providerIdParameter) {
        Assert.hasText(providerIdParameter, "provider id parameter must not be empty or null");
        this.providerIdParameter = providerIdParameter;
    }

    public String getProviderIdParameter() {
        return this.providerIdParameter;
    }

    protected String obtainCode(HttpServletRequest request) {
        return request.getParameter(this.codeParameter);
    }

    public void setCodeParameter(String codeParameter) {
        Assert.hasText(codeParameter, "code parameter must not be empty or null");
        this.codeParameter = codeParameter;
    }

    public String getCodeParameter() {
        return this.codeParameter;
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }

    public void setUsernameParameter(String usernameParameter) {
        this.usernameParameter = usernameParameter;
    }

    public String getUsernameParameter() {
        return this.usernameParameter;
    }

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

    public void setPasswordParameter(String passwordParameter) {
        this.passwordParameter = passwordParameter;
    }

    public String getPasswordParameter() {
        return this.passwordParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
}
